介紹一些虛擬選擇器,並且說明如何擴充虛擬選擇器。 :)
基本上 selector 嚴格分類來講有相當多種, slibing / parent ...etc
可以看這裡有分類
http://api.jquery.com/category/selectors/
CSS 提供給我們的那些相信大家都不陌生,ID(#)/Tag/class(.)/attr([])/child(>) ...etc
其中我要介紹的是一些算是比較不屬於 css3 規範的異類 selector ,
大家可能常用(至少我常用),但是不見得知道他的運作模式,
這裡我們就是要來告訴你他是什麼。:)
jQuery 的 selector 其實暗藏很多我們不知道的玄機,我會盡量再多介紹一些。
ok 不賣關子,我們開始談主題。
大家有沒有用過 pseudo selector ,像是這個範例?
<div class="test"></div>
<div class="test" style="display:none"></div>
<div class="test" style="display:none"></div>
<script>
alert($(".test:visible").length);
</script>
Runnable sample : http://jsfiddle.net/GGRD6/
":visible" selector,我相信大家都知道這不是 css 規格,但是它會動。:)
這種冒號開頭的選擇器,jQuery 稱之為虛擬選擇器。
(其實還再細分出一種是 pos selector
如 :eq / :lt / :odd ..etc ,剩下的都稱為 pseudo selector,
但這裡我先統稱為 pseudo selector 以便說明,原理是相近的。)
這時候我們就會需要來看看 jQuery 官方文件:
http://api.jquery.com/visible-selector/
Because :visible is a jQuery extension and not part of the CSS specification, queries using :visible cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :visible to select elements, first select the elements using a pure CSS selector, then use .filter(":visible").
(不負責翻譯)
因為 :visible 是一個 jQuery extension 而非 CSS 規格,使用 :visible 查詢時,
將無法利用到原生的 querySelectorAll() 函式,會比較慢。
要獲得最好效能,建議是先使用純 css 查詢再使用 filter 進行過濾。
重要的是這裡印證虛擬選擇器大多都不是 css 官方規格,
這裡他同時說兩件我們需要知道的事情,但效能部份我們先不管,
我們文末會回頭再談,我們先介紹虛擬選擇器的原理。
為了說明,我想 :visible 一個例子是不夠的,我再舉另一個例子 ":contains()" 好了,
這也是相當常見的例子,尋找指定元素內有包含特定字串的。
<div>
<div class="test" style="display:none">I am a div </div>
<div class="test" style="display:none"> I am also div </div>
<div class="test" style="display:none">I am still a div </div>
</div>
<script>
$(document.body).append(".test:contains('div') #=> "+ $(".test:contains('div')").length +"<br />");
$(document.body).append(".test:contains('still') #=> "+ $(".test:contains('still')").length +"<br />");
</script>
將會輸出
.test:contains('div') #=> 3
.test:contains('still') #=> 1
runnable sample http://jsfiddle.net/GGRD6/1/
官方文件 http://api.jquery.com/contains-selector/
當然還有 :not 這個一定是不可少的。
<div>
<div class="test test1" style="display:none">I am a div </div>
<div class="test test2" style="display:none"> I am also div </div>
<div class="test test3" style="display:none">I am still a div </div>
</div>
<script>
$(document.body).append(".test:not('.test1') #=> "+ $(".test:not('.test1')").length +"<br />");
$(document.body).append(".test:not('.test1,.test2') #=> "+ $(".test:not('.test1,.test2')").length +"<br />");
</script>
輸出
.test:not('.test1') #=> 2
.test:not('.test1,.test2') #=> 1
官方文件 http://api.jquery.com/not-selector/
你一定很好奇為什麼我要特別介紹這個,我可以一個一個作 selector 的介紹,
為什麼要介紹「pseudo selector 」,他有什麼好介紹的?
除了效能議題以外,其實我更重視的是他實作的邏輯,
我曾經很好奇 jQuery 是怎麼實作的而翻過很多次他的原始碼,
而其中這一個是我特別有印象的。:)
他有趣的地方在於一是實作跟虛擬選擇器的對應,另外就是他的可擴充性。
以我們 JS developer 來講,
碰到 selelector 這種帶著規則的「字串」東西其實蠻沒轍的,
他又不是 call 一個純 js function ,你如果想 trace 也要多繞好幾圈才看的懂。
而 pseudo selector 的條件定義呢,
就由 jQuery.expr.filters 跟 jQuery.expr.setFilters (for pos selectors) 掌管,
所以你可以翻原始碼,也可以用這種方式找出所有虛擬選擇器:
var filters = jQuery.expr.filters;
for(var i in filters ){
$(document.body).append(i+" #=> "+filters [i].toString()+"<br /><br />");
}
$(document.body).append("<div style='color:red;'>setFilters </div><br />");
filters = jQuery.expr.setFilters;
for(var i in filters ){
$(document.body).append(i+" #=> "+filters [i].toString()+"<br /><br />");
}
因為 details 有點多,想看細節的實作的可以移步 XD
http://jsfiddle.net/yAAqD/
翻翻 jQuery 1.7.2 的 4565 ~ 4686 行程式碼~(這只是其中部份。)
你會發現他基本上流程是這麼作:
1.先檢查是不是 pos selector ,是的話就跑 jQuery.expr.setFilters 出來作 filter。
(:nth , :eq, :gt, :lt, :first, :last, :even, :odd )
2.如果不是,就找 jQuery.expr.filters
如果後面還有跟其他 selector ,會再套疊後續的 operator 去做查詢。
你可能會好奇,如果我給 $(".test:myfilter") 會發生什麼事情,答案是會噴 error:
Syntax error, unrecognized expression: myfilter
所以理論上要擴充一個新的 filter ,直接擴充 jQuery.expr.filters 就可以了。
(如果你要擴充 set selector ,要改比較多東西,有興趣再問吧XD)
舉個例子,我今天比較機車,我想留 id 裡面有包含 tony 的元素,我可以這樣實作。
jQuery.expr.filters.isTony = function(elem){
return elem.id && elem.id.indexOf("tony") != -1;
}
//query with the pseudo selector
//$("div:isTony")
Sample http://jsfiddle.net/LMBgN/
至於效能,基本上就如官網文件所說。
基本上 "查詢" ($(selector) 或 $parent.find(selector)) ,
能不用虛擬 selector 就不該用;
而"過濾" ($set.filter(selector) or $set.is(selector )) 就很適合用。:)
文末的文末,補個題外話,我一直很訝異的是,
jQuery mobile 是用這麼髒的方式,來實作他們需要的內部用 pseudo selector。:P
有時候看看別人的實作,想想背後的理由,也是蠻有意思的。:P
// Monkey-patching Sizzle to filter the :jqmData selector
var oldFind = $.find,
jqmDataRE = /:jqmData\(([^)]*)\)/g;
$.find = function( selector, context, ret, extra ) {
selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
return oldFind.call( this, selector, context, ret, extra );
};
這篇因為想睡了講的比較亂,有興趣/疑問歡迎發問。:)